package pl.com.qapps.datastore.berkeley.impl;

import java.util.LinkedList;
import java.util.List;

import pl.com.qapps.datastore.base.QueryResult;
import pl.com.qapps.datastore.berkeley.LazyIterableWrapper;

import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityTranslator;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.storage.onestore.v3.OnestoreEntity.EntityProto;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.Transaction;

/**
 * Utility class that helps to make queries in Berkeley DB. Should work with all
 * types of queries. Do not call {@code #fetch(Database, Transaction)} twice
 * cause it will aggregate results with previous call!!!.
 * 
 * @author Jakub Dykowski
 * 
 */
class BerkeleyQuery {

	private final RangeScan scan;
	private final FetchOptions opts;

	private List<EntityProto> entities = new LinkedList<>();
	private int count;

	private boolean executed = false;

	public BerkeleyQuery(FetchOptions opts, DatabaseEntry firstKey,
			DatabaseEntry lastKey, boolean firstInclusive,
			boolean lastInclusive, boolean sortDuplicates) {
		if (opts == null)
			throw new NullPointerException("opts cannot be null");
		this.opts = opts;
		scan = new QueryScan(opts, firstKey, lastKey, firstInclusive,
				lastInclusive, sortDuplicates);
	}

	/**
	 * Do not run it twice!!!.
	 * 
	 * @param db
	 * @param txn
	 * @return
	 */
	public final QueryResult fetch(Database db,
			com.google.appengine.api.datastore.Transaction transaction) {
		if (executed)
			throw new IllegalStateException("query already made");
		executed = true;

		Transaction txn = (Transaction) transaction;

		// actual DB call
		final long skipped = scan.scan(db, txn);

		return new QueryResult() {

			@Override
			public int getSkippedResults() {
				return (int) skipped;
			}

			@Override
			public int resultSize() {
				return count;
			}

			@Override
			public Iterable<Entity> results() {
				// TODO consider performance tests, exclude lazy wrapping
				return new LazyIterableWrapper<EntityProto, Entity>(entities) {

					@Override
					protected Entity wrap(EntityProto item) {
						return EntityTranslator.createFromPb(item);
					}

				};
			}

		};
	}

	private class QueryScan extends RangeScan {

		private QueryScan(FetchOptions options, DatabaseEntry firstKey,
				DatabaseEntry lastKey, boolean firstInclusive,
				boolean lastInclusive, boolean sortDuplicates) {

			super(firstKey, lastKey, firstInclusive, lastInclusive,
					sortDuplicates, options.getOffset() != null ? options
							.getOffset() : 0L);
		}

		@Override
		protected boolean nextMatch(DatabaseEntry key, DatabaseEntry data) {
			if (opts.getLimit() != null && count >= opts.getLimit())
				return false;
			entities.add(EntityBindings.ENTITY_BINDING.entryToObject(key, data));
			count++;
			return true;
		}
	}

}
